{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 问题：python 中 yield 的用法\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "面试官想从这道题中考察面试人什么？\n",
    "yield 关键字也是经常会在面试中出现的考点，在平时工作中我们一般用 yield 来解决内存问题。候选人如果连这个都不懂的话，以后写的代码可能会时不时的因为数据过大导致代码崩溃。所以会使用 yield 是一个程序员的基本素养。\n",
    "\n",
    "答案\n",
    "yield表达式主要用在生成器函数和异步生成器函数中：\n",
    "\n",
    "在一个函数体内使用yield表达式会使这个函数返回一个生成器。在调用该函数的时候不会执行该函数，而是返回一个生成器对象。对这个对象执行for循环时，每次循环都会执行该函数内部的代码，执行到yield时，该函数就返回一个迭代值，下次迭代时，代码从yield的下一条语句继续执行，而函数的本地变量看起来和上次中断执行前是完全一样的，于是函数继续执行，直到再次遇到yield。\n",
    "在一个async def定义的函数体内使用yield表达式会让协程函数变成异步的生成器。\n",
    "官方问题用例：\n",
    "\n",
    "def gen():  # defines a generator function\n",
    "    yield 123\n",
    "\n",
    "async def agen(): # defines an asynchronous generator function\n",
    "    yield 123\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 拓展\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们先看一个最简单的生成器函数：\n",
    "\n",
    ">>> def gen_123():\n",
    "...    yield 1\n",
    "...    yield 2\n",
    "...    yield 3\n",
    "\n",
    ">>> gen = gen_123() # 调用生成器函数，返回一个生成器对象。\n",
    ">>> gen\n",
    "<generator object gen_123 at 0x102794f00>\n",
    ">>> gen_123\n",
    "<function __main__.gen_123>\n",
    ">>> next(gen) # 执行到第一个 yield 表达式，然后挂起，局部状态保留\n",
    "1\n",
    ">>> next(gen)\n",
    "2\n",
    ">>> next(gen)\n",
    "3\n",
    ">>> next(gen)\n",
    "Traceback (most recent call last):\n",
    "  File \"<stdin>\", line 1, in <module>\n",
    "StopIteration\n",
    "如果我们使用for对那个生成器对象执行循环，看看会发生什么\n",
    "\n",
    ">>> for gen_item in gen_123():\n",
    "...    print(gen_item)\n",
    "1\n",
    "2\n",
    "3\n",
    "这一次没有像上次那样抛出一个异常，这是因为使用for item in expression迭代的时候，它隐式地进行了以下几步：\n",
    "\n",
    "expression表达式被求值一次，产生一个可迭代对象，然后对它调用iter()，获取一个迭代器\n",
    "不断在获取到的迭代器上调用next函数得到的每一项，再对每一项执行一次子句体\n",
    "如果捕获到StopIteration异常就终止循环\n",
    "这里有几个概念大家需要分清楚：\n",
    "\n",
    "**可迭代对象 vs 迭代器**\n",
    "\n",
    "最常见的可迭代对象，比如一个列表alist=[1,2,3]，这是一个可迭代对象。我们可以使用for循环去迭代这个列表的每个成员。\n",
    "\n",
    "Tips: 可以返回一个迭代器的对象都可称之为可迭代对象，也可以这么说，可以使用for... in...的所有对象都是可迭代对象：列表(lists)、字符串、文件。\n",
    "\n",
    "可迭代对象的类都必须有一个方法__iter__，这个方法返回一个迭代器。\n",
    "\n",
    "Tips: 这里你就可以使用前面提到的自省方法dir(list)去查看属性。\n",
    "\n",
    "迭代器协议中规定迭代器必须支持两个方法：\n",
    "\n",
    "__next__：从容器中返回下一项\n",
    "__iter__: 返回迭代器对象本身\n",
    "手动迭代一个列表对象的过程：\n",
    "\n",
    ">>> a = [1,2] # <==== 可迭代对象\n",
    ">>> ia = iter(a) # <==== 返回的是一个迭代器\n",
    ">>> ia\n",
    "<listiterator object at 0x10c36df90>\n",
    ">>> next(ia) # 对迭代器进行next操作，而不是对可迭代对象操作\n",
    "1\n",
    "生成器 vs 迭代器\n",
    "\n",
    "上面我们提到了，当一个生成器函数被调用的时候，它返回一个生成器对象。它本身就支持next操作，所以，生成器其实是一种特殊的迭代器。不过，相比于自己动手去写一个迭代器类，用生成器往往要简单很多，因为不用去手动去写一个类，然后这个类还必须包含__next__方法和__iter__方法，只需要一个yiled关键字。所以，生成器是一种实现迭代器协议的便捷方式。\n",
    "\n",
    "下面我们再深入了解一下生成器的工作原理。当一个生成器函数被调用，然后一个生成器对象。我们先看看迭代两个对象，它们分别是调用下面两种方法返回的对象。\n",
    "\n",
    "def my_func1():\n",
    "    for i in range(5):\n",
    "        yield i\n",
    "\n",
    "def my_func2():\n",
    "    alist = []\n",
    "    for i in range(5):\n",
    "        alist.append(i)\n",
    "\n",
    "    return alist\n",
    "\n",
    ">>> g = my_func1()\n",
    ">>> l = my_func2()\n",
    ">>> for i in g:\n",
    "...    print(i)\n",
    ">>> for i in l:\n",
    "...    print(i)\n",
    "g是一个生成器对象，l是一个列表对象，它们都是可迭代对象，对它们进行迭代并打印出来的结果是一样的。但是，不同的是，我们可以一次性得到l中的所有成员。这就意味着，我们给l开了一段内存去存储它包含的所有成员。如果，my_func2返回的一个序列是一个非常大的序列呢？这就会有内存不够的风险，并且函数会一次性计算把得到的结果返回给l，这中间可能会消耗很多时间才能拿到你需要的结果。然而，调用my_func1就没有这个问题，for第一次调用从my_func1函数创建的生成器对象，函数将从头开始执行直到遇到yield，然后返回yield后的值作为第一次迭代的返回值，在下次调用next之前这个函数执行被挂起，挂起后，所有局部状态都被保留下来。生成器的调用者可以控制生成器函数yield多少次。所以，使用生成器，可以节省内存和高效运行。\n",
    "\n",
    "生成器函数 vs 生成器表达式\n",
    "\n",
    "生成器表达式是列表推倒式的生成器版本。生成器函数和生成器表达式都可以返回一个生成器对象。\n",
    "\n",
    ">>> a = (x*x for x in range(10)) # a是一个生成器对象\n",
    ">>> b = [x*x for x in range(10)] # b是一个用列表推导式生成的列表\n",
    "最后用一个图总结一下：\n",
    "\n",
    "图片描述\n",
    "\n",
    "对于这道题中涉及到异步生成器函数，简单提一下，一个异步生成器函数被调用时，它会返回一个名为异步生成器对象的异步迭代器。关于它的拓展内容会在协程中详细介绍。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 加分点"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "我把 2.6 和 2.7 的重点内容集中在了 super 方法和 yield 语句，这两点相关的知识我已经在问题中给大家详细的讲解了一遍。同样的，还是那句话：我们不可能在面试中把所有的知识点都和面试官复述一遍，这也不现实。但是只要你能清楚的描述出他们的工作原理这就够了，但是有两点大家还是要注意下：\n",
    "\n",
    "关于 super 方法的工作原理，必须要提到 MRO 工作链，这是重中之重！\n",
    "而关于生成器工作原理你需要说出函数将从头开始执行直到遇到yield，然后返回yield后的值作为第一次迭代的返回值，在下次调用next之前这个函数执行被挂起。这样的一个流程。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}